﻿using System;
using System.Reflection;
using System.CodeDom.Compiler;
using System.IO;

using Microsoft.CSharp;

using xEngine.Debugging;
using xEngine.Utils;

namespace xEngine.Scripting
{
    public class ScriptManager
    {
        #region Singleton

        private static ScriptManager inst = new ScriptManager();
        private ScriptManager()
        {
            _provider = new CSharpCodeProvider();
            _parameters = new CompilerParameters();

            _parameters.GenerateInMemory = true;
            _parameters.GenerateExecutable = false;

            RunCaseSensitive = true;
        }

        #endregion

        #region Member Variables

        private Assembly _assembly;
        private CSharpCodeProvider _provider;
        private CompilerParameters _parameters;

        #endregion

        #region Properties

        public static bool RunCaseSensitive { get; set; }

        #endregion

        #region Functions

        public static bool Compile(string file)
        {
            return Compile(new string[] { file});
        }
        public static bool Compile(string[] files)
        {
            return Compile(files, new string[] {});
        }
        public static bool Compile(string file, string references)
        {
            return Compile(new string[] { file }, new string[] { references } );
        }
        public static bool Compile(string file, string[] references)
        {
            return Compile(new string[] { file }, references);
        }
        public static bool Compile(string[] file, string references)
        {
            return Compile(file, new string[] { references });
        }
        public static bool Compile(string[] files, string[] references)
        {
            if (files == null)
                return false;

            inst._parameters.ReferencedAssemblies.Clear();
            inst._parameters.ReferencedAssemblies.Add("xEngine.dll");

            if(references != null)
                foreach(string reference in references)
                    inst._parameters.ReferencedAssemblies.Add(reference);

            string[] code = new string[files.Length];
            for (int i = 0; i < files.Length; i++)
                code[i] = DataLoader.GetStreamAsText(files[i]);

            CompilerResults res = inst._provider.CompileAssemblyFromSource(inst._parameters, code);

            if (res.Errors.HasErrors)
            {
                string prefix = LogWriter.LogPrefix;
                LogWriter.LogPrefix = "RTC_";
                LogWriter.TimeStamp = false;
                foreach (CompilerError error in res.Errors)
                    LogWriter.WriteLine = error;
                LogWriter.TimeStamp = true;
                LogWriter.LogPrefix = prefix;
                return false;
            }

            inst._assembly = res.CompiledAssembly;

            return true;
        }
        public static ScriptResponse Run(string nameSpace, string className, string methodName, bool isStatic, params object[] args)
        {
            if (inst._assembly == null)
                return new ScriptResponse(false, null);

            Type type = null;
            object instance = null, result = null;

            if (!isStatic)
            {
                if ((instance = inst._assembly.CreateInstance(nameSpace + "." + className)) != null)
                    type = instance.GetType();
            }
            else
                type = inst._assembly.GetType(nameSpace + "." + className, false, !RunCaseSensitive);

            BindingFlags bf = BindingFlags.Public;
            if(isStatic) bf |= BindingFlags.Static;
            if (!RunCaseSensitive) bf |= BindingFlags.IgnoreCase;

            if (type == null)
            {
                LogWriter.WriteLine = "ScriptManager: Type " + nameSpace + "." + className + " not found!";
                return new ScriptResponse(false, null);
            }

            MethodInfo mi = type.GetMethod(methodName, bf);

            if (mi == null)
            {
                LogWriter.WriteLine = "ScriptManager: Method " + type.FullName + "." + methodName + " not found!";
                return new ScriptResponse(false, null);
            }

            try
            {
                result = mi.Invoke(instance, args);
            }
            catch (Exception e)
            {
                LogWriter.WriteLine = "ScriptManager: " + e.Message;
                return new ScriptResponse(false, null);
            }

            return new ScriptResponse(true, result);
        }

        #endregion
    }

    public struct ScriptResponse
    {
        public bool Success;
        public object Result;
        public ScriptResponse(bool success, object result)
        {
            Success = success;
            Result = result;
        }
    }
}
